/*///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

FIVe3D
Flash Interactive Vector-based 3D
Mathieu Badimon  |  five3d@mathieu-badimon.com

http://five3D.mathieu-badimon.com  |  http://five3d.mathieu-badimon.com/archives/  |  http://code.google.com/p/five3d/

/*///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

import net.badimon.five3D.geom.Point;

class net.badimon.five3D.geom.Matrix {

	public var a:Number = 1;
	public var b:Number = 0;
	public var c:Number = 0;
	public var d:Number = 0;
	public var e:Number = 1;
	public var f:Number = 0;
	public var g:Number = 0;
	public var h:Number = 0;
	public var i:Number = 1;
	public var tx:Number = 0;
	public var ty:Number = 0;
	public var tz:Number = 0;

	function Matrix(a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, tx:Number, ty:Number, tz:Number) {
		if (a != undefined) {
			this.a = a;
			this.b = b;
			this.c = c;
			this.d = d;
			this.e = e;
			this.f = f;
			this.g = g;
			this.h = h;
			this.i = i;
			this.tx = tx;
			this.ty = ty;
			this.tz = tz;
		}
	}

	public function clone(Void):Matrix {
		return new Matrix(a, b, c, d, e, f, g, h, i, tx, ty, tz);
	}

	public function concat(m:Matrix):Void {
		var values:Object = {};
		values.a = a * m.a + b * m.d + c * m.g;
		values.b = a * m.b + b * m.e + c * m.h;
		values.c = a * m.c + b * m.f + c * m.i;
		values.tx = a * m.tx + b * m.ty + c * m.tz + tx;
		values.d = d * m.a + e * m.d + f * m.g;
		values.e = d * m.b + e * m.e + f * m.h;
		values.f = d * m.c + e * m.f + f * m.i;
		values.ty = d * m.tx + e * m.ty + f * m.tz + ty;
		values.g = g * m.a + h * m.d + i * m.g;
		values.h = g * m.b + h * m.e + i * m.h;
		values.i = g * m.c + h * m.f + i * m.i;
		values.tz = g * m.tx + h * m.ty + i * m.tz + tz;
		initialize(values);
	}

	private function initialize(values:Object):Void {
		for (var i:String in values) this[i] = values[i];
	}

	public function createBox(scalex:Number, scaley:Number, scalez:Number, rotationx:Number, rotationy:Number, rotationz:Number, tx:Number, ty:Number, tz:Number):Void {
		identity();
		if (rotationx != 0) rotateX(rotationx);
		if (rotationy != 0) rotateY(rotationy);
		if (rotationz != 0) rotateZ(rotationz);
		if (scalex != 0 || scaley != 0 || scalez != 0) scale(scalex, scaley, scalez);
		if (tx != 0 || ty != 0 || tz != 0) translate(tx, ty, tz);
	}

	private function identity(Void):Void {
		initialize({a:1, b:0, c:0, d:0, e:1, f:0, g:0, h:0, i:1, tx:0, ty:0, tz:0});
	}

	private function rotateX(angle:Number):Void {
		concat(new Matrix(1, 0, 0, 0, Math.cos(angle), -Math.sin(angle), 0, Math.sin(angle), Math.cos(angle), 0, 0, 0));
	}

	private function rotateY(angle:Number):Void {
		concat(new Matrix(Math.cos(angle), 0, Math.sin(angle), 0, 1, 0, -Math.sin(angle), 0, Math.cos(angle), 0, 0, 0));
	}

	private function rotateZ(angle:Number):Void {
		concat(new Matrix(Math.cos(angle), -Math.sin(angle), 0, Math.sin(angle), Math.cos(angle), 0, 0, 0, 1, 0, 0, 0));
	}

	private function scale(sx:Number, sy:Number, sz:Number):Void {
		concat(new Matrix(sx, 0, 0, 0, sy, 0, 0, 0, sz, 0, 0, 0));
	}

	public function transformPoint(pt:Point):Point {
		return new Point(a * pt.x + b * pt.y + c * pt.z + tx, d * pt.x + e * pt.y + f * pt.z + ty, g * pt.x + h * pt.y + i * pt.z + tz);
	}

	private function translate(dx:Number, dy:Number, dz:Number):Void {
		tx += dx;
		ty += dy;
		tz += dz;
	}

	public function getInverseCoordinates(x:Number, y:Number, viewdistance:Number):Point {
		var m:Array = [a, b, c, tx, d, e, f, ty, 0, 0, 1, 0, g / viewdistance, h / viewdistance, i / viewdistance, tz / viewdistance + 1];
		var m2:Array = invertTraditional(m);
		var w:Number = m2[12] * x + m2[13] * y + m2[15];
		return new Point((m2[0] * x + m2[1] * y + m2[3]) / w, (m2[4] * x + m2[5] * y + m2[7]) / w, 0);
	}

	private function invertTraditional(m:Array):Array {
		var m2:Array = [];
		var determinant:Number = getDeterminant4(m);
		for (var k:Number = 0;k < 4; k++) for (var j:Number = 0;j < 4; j++) m2[j * 4 + k] = getDeterminant3(extractMatrix3(m, j, k)) * (1 - ((j + k) % 2) * 2) / determinant;
		return m2;
	}

	private function getDeterminant4(m:Array):Number {
		return m[0] * getDeterminant3(extractMatrix3(m, 0, 0)) - m[1] * getDeterminant3(extractMatrix3(m, 1, 0)) + m[2] * getDeterminant3(extractMatrix3(m, 2, 0)) - m[3] * getDeterminant3(extractMatrix3(m, 3, 0));
	}

	private function extractMatrix3(m:Array, index1:Number, index2:Number):Array {
		var m2:Array = [];
		for (var k:Number = 0;k < 4; k++) if (k != index2) for (var j:Number = 0;j < 4; j++) if (j != index1) m2.push(m[j + k * 4]);
		return m2;
	}

	private function getDeterminant3(m:Array):Number {
		return m[0] * (m[4] * m[8] - m[7] * m[5]) - m[1] * (m[3] * m[8] - m[6] * m[5]) + m[2] * (m[3] * m[7] - m[6] * m[4]);
	}
}